範例1:
這個範例下面執行Myname(genius),會秀出Eason名子
var genius='anyone';
function Myname(){
var genius='Eason'
console.log(genius);
}
Myname();
範例2:
這個範例會秀出Eason 下一行 anyone。
var genius='anyone';
function Myname(){
var genius='Eason'
console.log(genius);
}
Myname();
console.log(genius);
我想試著用語法作用域(Lexical Scope)來解釋這件事情。
首先帶入的第一個觀念是:靜態作用域與動態作用域
JavaScript屬於靜態作用域,語法解析的時候就已經決定了作用域,且決定後不會再改變。
而它的作用域是一層一層向內的,如下圖,最外層是全域,裡面有Myname()跟Heyyou()兩個函式,而這兩個函式,各自是一個作用域,若函式內還有函式,裡面包住的那個函式也是一個作用域,
而當函式中需要某變數,而該作用域裏頭沒有這個變數,它就會開始向外查找,如果一直找不到這個變數,就會出現「ReferenceError: OOO is not defined」的錯誤訊息 (OOO是變數名稱)。
範例3: 向外查找
因為Myname()裡面沒有宣告genius所以向外查找,找到了全域變數中的genius,所以後來秀出了anyone
var genius='anyone'; //全域變數
function Myname(){
console.log(genius);
}
Myname();
所以我們回到範例1的部分,最下面那行Myname(); 呼叫了函式,裡面宣告了genius='Eason',並且使用console.log(genius); ,秀出了Eason,展現的是Myname()這個函式的作用域裡的執行狀況。
var genius='anyone';
function Myname(){
var genius='Eason'
console.log(genius);
}
Myname();
接著是範例2,如同範例1,我們知道Myname()函式秀出Eason,套用作用域的觀念,最底下那行console.log(genius);並不會秀出含式的內容,因為作用域是個別運作的,所以沒有因為先執行了函式而改寫了全域變數的var genius='anyone';而最底下的console.log會抓到的就是全域變數。
所以依照執行的順序,秀出的第一行Eason 第二行anyone。
var genius='anyone';
function Myname(){
var genius='Eason';
console.log(genius);
}
Myname();
console.log(genius);
最後一段範例,研究一下動態&靜態作用域的差別。
經過上面的敘述我們可以判斷JavaScript的靜態作用域,在語法解析時便已經決定了作用域,fn02定義的var num=2只會存在於該函式中,fn02中呼叫了fn01,fn01中的console.log(num);函式中變數沒有定義,所以向外查找,找到全域變數的var num=1;,所以秀出了1。
如果是動態作用域呢,程式碼執行的順序會是這樣子:
宣告var num=1;,直行fn02,裏頭從新宣告num=2,然後再fn02中呼叫fn01執行consolo.log,動態作用域再函式調用的時候才決定作用域,所以fn01就向上一層函式宣告的位置取得了num=2的結果。
var num=1;
function fn01(){
console.log(num);
}
function fn02(){
var num=2;
fn01();
}
fn02();
參考資料:
六角學院-JavaScript核心篇
連載於Eason的前端筆記